import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

/**
 * This is a helper class for the RocketAnalysisTool.
 */
public class RocketHelper
{
	private int headerWidth;
	private static String ProjectPath = System.getProperty("user.dir") + System.getProperty("file.separator") + "projects" + System.getProperty("file.separator");	

	/**
	 * The width in characters that headers should be.(boxes with headings)
	 * @param headerWidth - The number of characters wide for a headerBox
	 */
	public RocketHelper(int headerWidth)
	{
		this.headerWidth = headerWidth;
	}

	public Version newVersion(int releaseNumber)
	{
		Date releaseDate = null;
		int packageCount = 0;
		String versionID = "", versionReleaseDate;

		// Obtain Version ID from user
		System.out.println();
		RocketTools.writeHeader("Add Version", headerWidth);

		System.out.print("Enter unique Version identifier: ");
		versionID = RocketTools.readInput();

		// Obtain Version release date from user
		System.out.print("Enter the Release Date DD/MM/YYYY : ");
		versionReleaseDate = RocketTools.readInput();
		releaseDate = RocketTools.processDate(versionReleaseDate);

		// Obtains version class/Jar files listing from user
		LinkedList<String> sourceList = new LinkedList<String>();
		while (true)
		{
			System.out.print("Enter source class or jar file: ");
			String sourceFile = RocketTools.readInput().trim();

			if (sourceFile != null)
			{
				if (sourceFile.endsWith(".jar"))
				{
					sourceList = RocketTools.extractJar(sourceFile, sourceList);
					packageCount++;
				}
				else
					sourceList.add(sourceFile);
			}

			System.out.print("Enter another class or jar file (Y/N): ");
			String yesNoInput = RocketTools.readInput();

			if (!yesNoInput.equalsIgnoreCase("y")) break;
		}

		// Set persistent data
		Version version = new Version(versionID, releaseDate);
		version.setSourceList(sourceList);
		version.setPackageCount(packageCount);
		version.setReleaseSequenceNum(releaseNumber);

		return version;
	}

	public void analyseVersion(Version version)
	{
		System.out.println();
		RocketTools.writeHeader("Analyse Version", headerWidth);

		Hashtable<String, ClassMetricExtractor> extractors = new Hashtable<String, ClassMetricExtractor>();

		// Loop through each source file and analyse
		for (String sourceFile : version.getSourceList())
		{
			System.out.println("Analysing: " + sourceFile);

			try
			{
				ClassMetricExtractor cme = new ClassMetricExtractor(new FileInputStream(
						sourceFile));
				extractors.put(cme.className, cme);

				MetricClass metricClass = new MetricClass();
				metricClass.setFanOutClasses(cme.dependencies);
				metricClass.setFanOutCount(cme.fanOutCount);
				metricClass.setName(cme.className);
				metricClass.setSuperClassCount(cme.superClassCount);
				metricClass.setSuperClassName(cme.superClassName);
				metricClass.setFieldCount(cme.fieldCount);
				metricClass.setMethodCount(cme.methodCount);
				metricClass.setPublicMethodCount(cme.publicMethodCount);
				metricClass.setPrivateMethodCount(cme.privateMethodCount);
				metricClass.setProtectedMethodCount(cme.protectedMethodCount);
				metricClass.setByteCodeSizeInKb((new File(sourceFile).length())/1000.0);

				version.addMetricClass(metricClass);
			}
			catch (IOException e)
			{
				System.out.println("Cannot open or find " + sourceFile + "! " + sourceFile + " was not analysed.");
			}
		}

		// Calculate the fan in for each extractor
		calculateFanIn(extractors);

		// Update fan in.
		Enumeration e = extractors.elements();
		while (e.hasMoreElements())
		{
			// ClassMetricExtractor cme = (ClassMetricExtractor)e.nextElement();
			// Hashtable<String, String> table = cme.getFanIn();
			// Enumeration e2 = table.elements();
			//			
			// while (e2.hasMoreElements())
			// {
			// System.out.println(e.nextElement());
			// }
			ClassMetricExtractor cme = (ClassMetricExtractor) e.nextElement();
			MetricClass metricClass = version.getMetricClass(cme.className);
			metricClass.setFanIn(cme.getFanInClasses());
			metricClass.setFanInCount(cme.getFanInClasses().size());
		}
	}

	private void calculateFanIn(Hashtable<String, ClassMetricExtractor> extractors)
	{
		Enumeration e = extractors.elements();
		while (e.hasMoreElements())
		{
			ClassMetricExtractor cme = (ClassMetricExtractor) e.nextElement();

			LinkedList<String> fanOutNodes = new LinkedList<String>(cme.dependencies);

			for (String fanOutNode : fanOutNodes)
			{
				try
				{
					ClassMetricExtractor cmeIn = extractors.get(fanOutNode);
					cmeIn.addFanIn(cme.className);
				}
				catch (NullPointerException err)
				{
				}
			}
		}
	}

	/**
	 * Displays the fanIn data for the specified ClassMetricExtractor
	 * @param cme - the ClassMetricExtractor.
	 */
	private void displayFanIn(ClassMetricExtractor cme)
	{
		System.out.println("Printing FanIn for class: " + cme.className);
		LinkedList<String> fanInClasses = cme.getFanInClasses();
		Iterator i = fanInClasses.iterator();

		while (i.hasNext())
			System.out.println(i.next());
	}

	public void save(Project project)
	{

		// Uses currentProject name as fileName
		String fileName = RocketHelper.getProjectPath(project)
				+ project.getName().concat(".xml");
		save(project, fileName);
	}

	public void save(Project project, String fileName)
	{
		System.out.println("Project path: " + RocketHelper.getProjectPath(project));
		RocketTools.writeHeader("Save Project", headerWidth);

		try
		{
			File f = new File(RocketHelper.getProjectPath(project));
			
			if (!f.exists()) 
			{
				if(!f.mkdir())
					throw new IOException("Unable to create directory. (" + f.getPath() + ")");					
			}
			// Serialize Project to XML string
			XStream xStream = new XStream(new DomDriver());
			String xmlData = xStream.toXML(project);
			// Write XML to file
			PrintWriter pw = new PrintWriter(new FileOutputStream(fileName));
			pw.write(xmlData);
			pw.close();

			System.out.println("\nProject saved to: " + fileName + "\n");
		}
		catch (FileNotFoundException e)
		{
			System.err.println("File not found. (" + fileName + ")");
		}
		catch(IOException e)
		{
			System.err.println(e.getMessage());
		}
	}

	/**
	 * Prompts the user to specify the path to a RAT project file and loads the project into
	 * memory.
	 * @return - The loaded project
	 * @throws - FileNotFoundException
	 */
	public Project load()
	{
		Project toLoad = null;		

		System.out.println();
		RocketTools.writeHeader("Load Project", headerWidth);

		while(true)
		{
			System.out.print("Enter the project file location: ");
			String projectFilePath = RocketTools.readInput().trim();
	
			try
			{
				XStream xStream = new XStream(new DomDriver());
				toLoad = (Project) xStream.fromXML(new FileInputStream(projectFilePath));
				break;
			}
			catch (FileNotFoundException e)
			{
				System.err.println("Project could not be loaded. " + projectFilePath
								   + " not found. Please try again.");
			}
		}

		return toLoad;
	}

	public void produceChart(Project project, boolean relative)
	{
		ArrayList<String> selectedVersions = new ArrayList<String>();
		ChartGenerator chartGen = new ChartGenerator();

		RocketTools.writeHeader("Generate Charts", headerWidth);
		String[] options = {"Growth Chart", "FanIn Histogram", "FanOut Histogram"};
		int chartSelection = RocketTools.createMenuAndWait(options);

		// Growth Chart
		switch (chartSelection)
		{
			case 0 :

				doGrowth(project, chartGen, chartSelection);
				break;
			case 1 : // Histograms
			case 2 :
			{
				doHistogram(project, relative, selectedVersions, chartGen, chartSelection);
				break;
			}
		}
	}

	/**
	 * @param project
	 * @param relative
	 * @param selectedVersions
	 * @param chartGen
	 * @param chartSelection
	 */
	private void doHistogram(Project project, boolean relative,
			ArrayList<String> selectedVersions, ChartGenerator chartGen, int chartSelection)
	{
		ChartVersion[] versionsToChart;
		versionsToChart = histogramChartSelection(project, selectedVersions);
		project.setChartVersions(versionsToChart);

		switch (chartSelection)
		{
			case 1 :
				chartGen.constructFanInChart(project, relative);
				break;
			case 2 :
				chartGen.constructFanOutChart(project, relative);
				break;
		}
	}

	/**
	 * @param project
	 * @param selectedVersions
	 * @return
	 */
	private ChartVersion[] histogramChartSelection(Project project,
			ArrayList<String> selectedVersions)
	{
		ChartVersion[] versionsToChart;
		ArrayList<String> versionIds = project.versionListing();
		versionIds.add("Exit");

		// Prompt user to enter the version id's to chart
		while (true)
		{
			int selection = RocketTools.createMenuAndWait(versionIds.toArray());
			System.out.println("selection:" + selection + " versionSize: "
					+ versionIds.size());
			if (selection == versionIds.size() - 1) break;

			selectedVersions.add(versionIds.get(selection));
			versionIds.remove(selection);
		}
		versionsToChart = new ChartVersion[selectedVersions.size()];

		// Load the appropriate ChartVersion objects
		for (int i = 0; i < selectedVersions.size(); i++)
		{
			Version selectedVersion = project.getVersion(selectedVersions.get(i).toString());
			RocketTools.writeHeader(selectedVersion.getIdentifier() + " Chart Settings",
					headerWidth);
			int binCount = (int) RocketTools.promptAndWait("Bin count");
			double minBin = RocketTools.promptAndWait("Minimum bin size");
			double maxBin = RocketTools.promptAndWait("Maximum bin size");

			versionsToChart[i] = new ChartVersion(selectedVersion, binCount, minBin, maxBin);
		}

		return versionsToChart;
	}

	/**
	 * @param project
	 * @param chartGen
	 * @param options
	 */
	private void doGrowth(Project project, ChartGenerator chartGen, int chartSelection)
	{
		String[] chartOptions = {"Classes", "Methods", "Packages", "Byte-Code"};
		RocketTools.writeHeader("Create Project Growth Chart", RocketAnalysisTool.headerWidth);
		chartSelection = RocketTools.createMenuAndWait(chartOptions);

		int maxRSN = project.generateReleaseSequenceNum() - 1;

		int startRSN = RocketTools.promptBoundsCheckAndWait("Enter Starting RSN (min=1, max=" + maxRSN + ")", 1, maxRSN);
		int endRSN = RocketTools.promptBoundsCheckAndWait("Enter Final RSN (min=" + startRSN + ", max=" + maxRSN + ")", startRSN, maxRSN);
		int increment = RocketTools.promptBoundsCheckAndWait("Enter Increment Range", 1, endRSN);

		
		chartGen.constructGrowthChart(project, startRSN, endRSN, increment, chartSelection);
	}



	/**
	 * Returns the path where this project is stored.
	 * @param project
	 * @return
	 */
	public static String getProjectPath(Project project)
	{
		return ProjectPath + project.getName() + "\\";
	}
}
